///<reference path='../SharedWorker.d.ts' />
import { commandTypeOnlyQuery, commandTypeOnlySubscribe, commandTypeQueryAndSubScribe, commandTypePageQuery, commandTypeProxyInvoke, commandTypeProxyList, commandTypeMsgDefList, commandTypePageControlResponse, commandTypeProxyLogin, commandTypeProxyLogout } from '../model/contexts';
import { IContextBroker, IContext, CONTEXT_BROKER_LOGGER, IPersist } from 'p-elite-app-contextbroker';
import { Injectable, Inject, Optional, OnDestroy, NgZone } from '@angular/core';
import { CancelSubCommandContext, QNSContext, ProxyInvokeContext, ConnectToPilotContext, PilotFuncInvokeContext, LoginToPilotContext, PublishBOContext, ProxyLoginCommandContext, ProxyLogoutCommandContext, RemoteProxyInvokeContext } from '../model/contexts';
import { UUID } from '../UUID';
import { CommandContext } from '../model/command.context';
import { SHARED_WORKER_URL, USE_SHARED_WORKER } from './elite-context-broker';
import { IPilotContextBroker } from './pilot-context-broker-interface';
import { EliteWorker } from './elite-worker';
import { Observable, Subject, ReplaySubject, Subscription, Observer } from "rxjs/Rx";
export class PilotContextBroker implements IPilotContextBroker, OnDestroy {

    observersByRequestId: Map<string, any> = new Map<string, any>();
    pilotWorker: EliteWorker;
    pilotObserver: Subject<any>;
    pilotConnectObserver = new Subject();
    pilotDisConnectObserver = new Subject();
    pilotConnectFailObserver = new Subject();
    pilotReConnectFailObserver = new Subject();
    pilotErrorObserver = new Subject();
    pilotLoginInfoObserver = new ReplaySubject(1);
    pilotLoginDeferred: Deferred;
    funcDeferredByUUID: Map<string, Deferred> = new Map<string, any>();
    proxyMethodMap: Map<string, string> = new Map<string, string>();
    constructor(
        private zone: NgZone,
        workerUrl?: string,
        useSharedWorker?: boolean,
        private logger?: any
    ) {
        this.initAndStartPilotWorker(workerUrl, useSharedWorker);
    }

    public query(context: QNSContext, optionObserver?: OptionObserver) {
        context.command = commandTypeOnlyQuery;
        if (!context.requestId) {
            context.requestId = UUID.next();
        }
        if (optionObserver) {
            this.setUpOptionObserver(optionObserver);
        }
        this.pilotWorker.postMessage(context);
        console.time(context.requestId);
        console.log('send request', context.requestId, context.query);
        const reusltObservable = Observable.create(function (observer) {
            const filterStream = this.pilotObserver.filter(function (data) {
                return data.requestId === context.requestId;
            });
            return filterStream.subscribe((data) => {
                this.zone.run(() => {
                    if (data.errorCode) {
                        observer.error({ errorCode: data.errorCode, errorMsg: data.payload });
                        if (optionObserver && optionObserver.onErrorObserver) {
                            optionObserver.onErrorObserver.next({ errorCode: data.errorCode, errorMsg: data.payload });
                            optionObserver.onErrorObserver.complete();
                        }
                    } else {
                        if (data.marker && data.endMarker) {
                            observer.complete();
                            if (optionObserver && optionObserver.onEndObserver) {
                                optionObserver.onEndObserver.next(data);
                                optionObserver.onEndObserver.complete();
                                console.timeEnd(context.requestId);
                            }
                        } else if (data.marker && data.startMarker) {
                            if (optionObserver && optionObserver.onStartObserver) {
                                optionObserver.onStartObserver.next(data);
                                optionObserver.onStartObserver.complete();
                            }
                        } else {
                            if (Array.isArray(data.payload)) {
                                if (context.useBatchModel) {
                                    observer.next(data.payload);
                                } else {
                                    data.payload.forEach(p => {
                                        observer.next(p);
                                    });
                                }
                            } else {
                                observer.next(data.payload);
                            }
                        }
                    }
                });
            });
        }.bind(this));
        return reusltObservable as Observable<any>;
    }

    public sub(context: QNSContext, optionObserver?: OptionObserver) {
        context.command = commandTypeOnlySubscribe;
        if (!context.requestId) {
            context.requestId = UUID.next();
        }
        if (optionObserver) {
            this.setUpOptionObserver(optionObserver);
        }
        let subject: Subject<any> = this.observersByRequestId.get(context.requestId);
        if (subject == null) {
            subject = new Subject<any>();
            this.observersByRequestId.set(context.requestId, subject);
            this.pilotWorker.postMessage(context);
        }
        const reusltObservable = Observable.create(function (observer) {
            const filterStream = this.pilotObserver.filter(function (data) {
                return data.requestId === context.requestId;
            });
            return filterStream.subscribe((data) => {
                this.zone.run(() => {
                    if (data.errorCode) {
                        observer.error({ errorCode: data.errorCode, errorMsg: data.payload });
                        if (optionObserver && optionObserver.onErrorObserver) {
                            optionObserver.onErrorObserver.next({ errorCode: data.errorCode, errorMsg: data.payload });
                            optionObserver.onErrorObserver.complete();
                        }
                    } else {
                        if (data.marker && data.endMarker) {
                            if (optionObserver && optionObserver.onEndObserver) {
                                optionObserver.onEndObserver.next(data);
                                optionObserver.onEndObserver.complete();
                            }
                        } else if (data.marker && data.startMarker) {
                            if (optionObserver && optionObserver.onStartObserver) {
                                optionObserver.onStartObserver.next(data);
                                optionObserver.onStartObserver.complete();
                            }
                        }else{
                            if (Array.isArray(data.payload)) {
                                if (context.useBatchModel) {
                                    observer.next(data.payload);
                                } else {
                                    data.payload.forEach(p => {
                                        observer.next(p);
                                    });
                                }
                            } else {
                                observer.next(data.payload);
                            }
                        }
                    }
                });
            });
        }.bind(this));
        return reusltObservable as Observable<any>;

    }

    public qns(context: QNSContext, optionObserver?: OptionObserver) {
        context.command = commandTypeQueryAndSubScribe;
        if (!context.requestId) {
            context.requestId = UUID.next();
        }
        if (optionObserver) {
            this.setUpOptionObserver(optionObserver);
        }
        let subject: Subject<any> = this.observersByRequestId.get(context.requestId);
        if (subject == null) {
            subject = new Subject<any>();
            this.observersByRequestId.set(context.requestId, subject);
            this.pilotWorker.postMessage(context);
        }
        const reusltObservable = Observable.create(function (observer) {
            const filterStream = this.pilotObserver.filter(function (data) {
                return data.requestId === context.requestId;
            });
            return filterStream.subscribe((data) => {
                this.zone.run(() => {
                    if (data.errorCode) {
                        // observer.error({ errorCode: data.errorCode, errorMsg: data.payload });
                        if (optionObserver && optionObserver.onErrorObserver) {
                            optionObserver.onErrorObserver.next({ errorCode: data.errorCode, errorMsg: data.payload });
                            optionObserver.onErrorObserver.complete();
                        }
                    } else {
                        if (data.marker && data.endMarker) {
                            if (optionObserver && optionObserver.onEndObserver) {
                                optionObserver.onEndObserver.next(data);
                                optionObserver.onEndObserver.complete();
                            }
                        } else if (data.marker && data.startMarker) {
                            if (optionObserver && optionObserver.onStartObserver) {
                                optionObserver.onStartObserver.next(data);
                                optionObserver.onStartObserver.complete();
                            }
                        } else {
                            if (Array.isArray(data.payload)) {
                                if (context.useBatchModel) {
                                    observer.next(data.payload);
                                } else {
                                    data.payload.forEach(p => {
                                        observer.next(p);
                                    });
                                }
                            } else {
                                observer.next(data.payload);
                            }
                        }
                    }
                });
            });
        }.bind(this));
        return reusltObservable as Observable<any>;

    }

    public invokeProxy(context: ProxyInvokeContext, optionObserver?: OptionObserver) {
        if (!context.requestId) {
            context.requestId = UUID.next();
        }
        if (optionObserver) {
            this.setUpOptionObserver(optionObserver);
        }
        this.pilotWorker.postMessage(context);
        const reusltObservable = Observable.create(function (observer) {
            const filterStream = this.pilotObserver.filter(function (data) {
                return data.requestId === context.requestId;
            });
            return filterStream.subscribe((data) => {
                this.zone.run(() => {
                    if (data.errorCode) {
                        observer.error({ errorCode: data.errorCode, errorMsg: data.errorMsg || data.payload });
                        if (optionObserver && optionObserver.onErrorObserver) {
                            optionObserver.onErrorObserver.next(data);
                            optionObserver.onErrorObserver.complete();
                        }
                    } else {
                        observer.next(data.result);
                        observer.complete();
                    }
                });
            });
        }.bind(this));
        return reusltObservable as Observable<any>;
    }

    public connectToPilot(contextToPilot: ConnectToPilotContext) {
        console.log('invoke connectToPilot', contextToPilot, this.pilotWorker);
        this.pilotWorker.postMessage(contextToPilot);
    }

    public cancelSubscription(subscription: Subscription, requestId?: string) {
        if (requestId) {
            let context: CancelSubCommandContext = new CancelSubCommandContext(requestId);
            this.pilotWorker.postMessage(context);
            this.observersByRequestId.delete(requestId);
        }

        if (subscription && !subscription.closed) {
            subscription.unsubscribe();
        }
    }

    public uuid(): string {
        return UUID.next();
    }

    public setUpOptionObserver(optionObserver: OptionObserver) {
        if (optionObserver) {
            if (optionObserver.onPilotConnectedObserver) {
                this.pilotConnectObserver.subscribe(optionObserver.onPilotConnectedObserver);
            }
            if (optionObserver.onPilotDisConnectObserver) {
                this.pilotDisConnectObserver.subscribe(optionObserver.onPilotDisConnectObserver);
            }
            if (optionObserver.onPilotReConnectFailObserver) {
                this.pilotReConnectFailObserver.subscribe(optionObserver.onPilotReConnectFailObserver);
            }
            if (optionObserver.onPilotConnectFailObserver) {
                this.pilotConnectFailObserver.subscribe(optionObserver.onPilotConnectFailObserver);
            }
            if (optionObserver.onPilotErrorObserver) {
                this.pilotErrorObserver.subscribe(optionObserver.onPilotErrorObserver);
            }
            if (optionObserver.onPilotLoginInfoObserver) {
                this.pilotLoginInfoObserver.subscribe(optionObserver.onPilotLoginInfoObserver);
            }
        }
    }

    public disconnect(): void {
        this.pilotWorker.postMessage({ contextClass: 'DisconnectToPilotContext' });
    }

    public invokePilotFunc(context: PilotFuncInvokeContext): Promise<any> {
        if (!context.uuid) {
            context.uuid = UUID.next();
        }
        let pilotFuncPromise = new Promise((resolve, reject) => {
            this.funcDeferredByUUID.set(context.uuid, {
                "resolve": resolve,
                "reject": reject
            } as Deferred);
        });
        this.pilotWorker.postMessage(context);
        return pilotFuncPromise;
    }

    public loginToPilot(loginToPilotContext: LoginToPilotContext): Promise<any> {
        console.log('invoke loginToPilot', loginToPilotContext, this.pilotWorker);
        let pilotLoginPromise = new Promise((resolve, reject) => {
            this.pilotLoginDeferred = {
                "resolve": resolve,
                "reject": reject
            } as Deferred;
        });
        this.pilotWorker.postMessage(loginToPilotContext);

        return pilotLoginPromise;
    }

    public publish(context: PublishBOContext): Promise<any> {
        if (!context.requestId) {
            context.requestId = UUID.next();
        }
        context.payload.payload = JSON.stringify(context.payload);
        context.payload.messageDefinition = context.messageDefinition;
        this.pilotWorker.postMessage(context);
        const reusltObservable: Observable<any> = Observable.create(function (observer) {
            const filterStream = this.pilotObserver.filter(function (data) {
                return data.requestId === context.requestId;
            });
            return filterStream.subscribe((data) => {
                this.zone.run(() => {
                    if (data.errorCode) {
                        observer.error({ errorCode: data.errorCode, errorMsg: data.errorMsg || data.payload });

                    } else {
                        observer.next(data.payload);
                        observer.complete();
                    }
                });
            });
        }.bind(this));

        return reusltObservable.toPromise();

    }

    proxyLogin(context: ProxyLoginCommandContext): Promise<any> {
        if (!context.requestId) {
            context.requestId = UUID.next();
        }
        this.pilotWorker.postMessage(context);
        const reusltObservable: Observable<any> = Observable.create(function (observer) {
            const filterStream = this.pilotObserver.filter(function (data) {
                return data.requestId === context.requestId;
            });
            return filterStream.subscribe((data) => {
                this.zone.run(() => {
                    if (data.errorCode) {
                        observer.error({ errorCode: data.errorCode, errorMsg: data.errorMsg || data.payload });

                    } else {
                        observer.next(data);
                        observer.complete();
                    }
                });
            });
        }.bind(this));
        return reusltObservable.toPromise();
    }

    proxyLogout(context: ProxyLogoutCommandContext): Promise<any> {
        if (!context.requestId) {
            context.requestId = UUID.next();
        }
        this.pilotWorker.postMessage(context);
        const reusltObservable: Observable<any> = Observable.create(function (observer) {
            const filterStream = this.pilotObserver.filter(function (data) {
                return data.requestId === context.requestId;
            });
            return filterStream.subscribe((data) => {
                this.zone.run(() => {
                    if (data.errorCode) {
                        observer.error({ errorCode: data.errorCode, errorMsg: data.errorMsg || data.payload });

                    } else {
                        for (let key of this.proxyMethodMap.keys()) {
                            if (this.proxyMethodMap.has(key) === context.proxyId) {
                                this.proxyMethodMap.delete(key);
                            }
                        }
                        observer.next(data);
                        observer.complete();
                    }
                });
            });
        }.bind(this));
        return reusltObservable.toPromise();
    }

    invokeRemoteProxy(context: RemoteProxyInvokeContext): Promise<any> {
        if (!context.newProxyInstance && this.proxyMethodMap.has(context.className)) {
            context.proxyId = this.proxyMethodMap.get(context.className);
            return this.remoteProxy(context);
        } else {
            const proxyLoginContext: ProxyLoginCommandContext = new ProxyLoginCommandContext();
            proxyLoginContext.className = context.className;
            proxyLoginContext.domain = context.domain;
            proxyLoginContext.region = context.region;
            return this.proxyLogin(proxyLoginContext).then(data => {
                context.proxyId = data.proxyId;
                this.proxyMethodMap.set(context.className, context.proxyId);
                return this.remoteProxy(context);
            });
        }

    }

    remoteProxy(context: RemoteProxyInvokeContext): Promise<any> {
        if (!context.requestId) {
            context.requestId = UUID.next();
        }
        this.pilotWorker.postMessage(context);
        const reusltObservable: Observable<any> = Observable.create(function (observer) {
            const filterStream = this.pilotObserver.filter(function (data) {
                return data.requestId === context.requestId;
            });
            return filterStream.subscribe((data) => {
                this.zone.run(() => {
                    if (data.errorCode) {
                        observer.error({ errorCode: data.errorCode, errorMsg: data.errorMsg || data.payload });

                    } else {
                        observer.next(data);
                        observer.complete();
                    }
                });
            });
        }.bind(this));

        return reusltObservable.toPromise();
    }

    private initAndStartPilotWorker(sharedWorkerUrl?: string, useSharedWorker?: boolean) {
        let url = sharedWorkerUrl ? sharedWorkerUrl : './js/pilotWorker.js';
        this.pilotObserver = new Subject();
        this.pilotWorker = new EliteWorker(url, useSharedWorker);
        this.pilotWorker.onmessage((e) => {
            this.zone.run(() => {
                const object = e.data;
                if (object.contextClass) {
                    this.processContextMsg(object);
                } else {
                    this.pilotObserver.next(object);
                }
            });
        });

        this.pilotWorker.onerror((event) => {
            let friendlyMsg: string;

            if (event)
                friendlyMsg = `${event.message} (${event.filename} : ${event.lineno})`;

            if (this.logger && this.logger.error) {
                this.logger.error(friendlyMsg);
            } else {
                throw new Error(friendlyMsg);
            }
        });

        this.pilotWorker.start();
    }

    private processContextMsg(object) {
        switch (object.contextClass) {
            case 'loginInfoContext':
                this.pilotLoginInfoObserver.next(object.event || object);
                break;
            case 'onPilotConnectContext':
                this.pilotConnectObserver.next(object.event);
                break;
            case 'onPilotDisConnectContext':
                this.pilotDisConnectObserver.next(object.event);
                break;
            case 'onPilotConnectFailContext':
                this.pilotConnectFailObserver.next(object.event);
                break;
            case 'onPilotReConnectFailContext':
                this.pilotReConnectFailObserver.next(object.event);
                break;
            case 'onPilotErrorContext':
                this.pilotErrorObserver.next(object.event);
                break;
            case 'LoginToPilotContext':
                if (this.pilotLoginDeferred) {
                    if (object.error) {
                        this.pilotLoginDeferred.reject(object.error);
                    } else {
                        this.pilotLoginDeferred.resolve(object.loginResponse);
                    }
                    this.pilotLoginDeferred = null;
                }
                break;
            case 'PilotFuncResultContext':
                let funcDeferred = this.funcDeferredByUUID.get(object.uuid);
                if (funcDeferred) {
                    if (object.error) {
                        funcDeferred.reject(object.error);
                    } else {
                        funcDeferred.resolve(object.result);
                    }
                    this.funcDeferredByUUID.delete(object.uuid);
                }
                break;
            default:
                break;
        }
    }

    ngOnDestroy(): void {
        this.pilotWorker.ngOnDestroy();
    }
}

export interface OptionObserver {
    onErrorObserver?: Observer<any>;
    onStartObserver?: Observer<any>;
    onEndObserver?: Observer<any>;
    onPilotConnectedObserver?: Observer<any>;
    onPilotDisConnectObserver?: Observer<any>;
    onPilotConnectFailObserver?: Observer<any>;
    onPilotReConnectFailObserver?: Observer<any>;
    onPilotErrorObserver?: Observer<any>;
    onPilotLoginInfoObserver?: Observer<any>;
    context?: CommandContext;
}

export interface Deferred {
    resolve: Function;
    reject: Function;
}